package com.cr.dataprotect.manage;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.cr.dataprotect.common.MyConstants;
import com.cr.dataprotect.exception.IntegrityDataException;
import com.cr.dataprotect.provider.AbstractProvider;
import com.cr.dataprotect.sdk.CryptoApi;
import com.cr.dataprotect.sdk.IntegrityApi;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.*;

/**
 * 数据完整性保护
 *
 * @author ChenRu1
 * @Description
 * @Date 16:30 2023/12/5
 **/
@Slf4j
@Service
public class IntegrityManage extends AbstractProvider {

    private static final String SAVE_TABLE = "savetable";
    private static final String TABLE = "table";

    @Lazy
    @Autowired
    private IntegrityApi integrityApi;
    @Autowired(required = false)
    private CryptoApi cryptoApi;

    /**
     * 处理完整性保护
     *
     * @param invocation
     * @param fieldConfig
     * @throws Throwable
     */
    public void handleIntegrity(Invocation invocation, Map<Class, Map<String, Object>> fieldConfig) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) PluginUtils.realTarget(invocation.getTarget());
        Object parameterObject = parameterHandler.getParameterObject();
        MapperMethod.ParamMap<Object> paramMap = null;
        MetaObject metaObject = SystemMetaObject.forObject(parameterHandler);
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();

        // MyBatis MapperMethod.ParamMap 类型，key - value，value 可能为 PO 类型
        if (parameterObject instanceof MapperMethod.ParamMap) {
            paramMap = (MapperMethod.ParamMap<Object>) parameterObject;
        } else {
            paramMap = new MapperMethod.ParamMap<>();
            paramMap.put("", parameterObject);
        }

        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            String paramName = entry.getKey();
            Object paramValue = entry.getValue();
            //paramValue 有可能为空
            if (paramValue == null) {
                continue;
            }
            Class<?> valueClass = paramValue.getClass();
            if (!fieldConfig.containsKey(valueClass)) {
                continue;
            }
            //只有存在表映射的实体类才进行处理
            Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");
            boundSqlField.setAccessible(true);
            BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            if (CollectionUtils.isNotEmpty(parameterMappings)) {
                Map<String, Object> valMap = fieldConfig.get(valueClass);
                String[] sensitiveFields = (String[]) valMap.get("properties");
                StringBuilder inData = new StringBuilder();
                Map<String, Object> inDataMap = new LinkedHashMap<>();


                for (ParameterMapping parameterMapping : parameterMappings) {
                    if (parameterMapping.getMode() != ParameterMode.OUT) {
                        String propertyName = parameterMapping.getProperty();

                        String trimmedParamName = propertyName;
                        if (StringUtils.isNotEmpty(paramName) && trimmedParamName.startsWith(paramName + ".")) {
                            trimmedParamName = trimmedParamName.substring(paramName.length() + 1);
                            log.debug("origin param name: {}, trimmed param name:{}", propertyName, trimmedParamName);
                        }

                        if (ArrayUtils.contains(sensitiveFields, trimmedParamName)) {
                            Configuration configuration = mappedStatement.getConfiguration();
                            MetaObject newMetaObject = configuration.newMetaObject(paramValue);
                            String val = newMetaObject.getValue(trimmedParamName).toString();
                            inData.append(val);
                            inDataMap.put(trimmedParamName, val);
                        }
                    }
                }
                if (inData.length() > 0) {
                    String secretKey = getSecretKey(fieldConfig, valueClass, MyConstants.INTEGRITY);
                    if (ObjectUtils.isNotEmpty(secretKey)) {
                        String id = !ObjectUtils.isEmpty(valMap.get("id")) ? valMap.get("id").toString() : "";
                        String table = !ObjectUtils.isEmpty(valMap.get(TABLE)) ? valMap.get(TABLE).toString() : "";
                        String savetable = !ObjectUtils.isEmpty(valMap.get(SAVE_TABLE)) ? valMap.get(SAVE_TABLE).toString() : "";
                        Configuration configuration = mappedStatement.getConfiguration();
                        MetaObject newMetaObject = configuration.newMetaObject(paramValue);
                        String idVal = newMetaObject.getValue(id).toString();

                        if (sqlCommandType == SqlCommandType.INSERT) {
                            String integrityData = cryptoApi.mac(inData.toString(), secretKey);
                            integrityApi.saveIntegrityData(idVal, JSONUtil.toJsonStr(inDataMap), integrityData, table, savetable);
                        } else if (sqlCommandType == SqlCommandType.UPDATE) {
                            String integrityData = cryptoApi.mac(inData.toString(), secretKey);
                            integrityApi.updateIntegrityData(idVal, JSONUtil.toJsonStr(inDataMap), integrityData, table, savetable);
                        }
                    } else {
                        log.error("密钥不存在，请检查密钥！");
                    }

                }
            }
        }
    }

    /**
     * 校验完整性保护
     *
     * @param result
     * @param fieldConfig
     * @return
     */
    public void validateIntegrity(Object result, Map<Class, Map<String, Object>> fieldConfig) {
        if (result instanceof Collection) {
            Collection resultList = (Collection) result;
            if (CollectionUtils.isNotEmpty(resultList)) {
                for (Object o : resultList) {
                    if (null == o) {
                        continue;
                    }
                    //判断是否属于配置的加密类
                    if (!fieldConfig.containsKey(o.getClass())) {
                        //返回结果不是sensitive中配置的类型
                        break;
                    }

                    Map<String, Object> valMap = fieldConfig.get(o.getClass());
                    String[] sensitiveFields = (String[]) valMap.get("properties");
                    String id = !ObjectUtils.isEmpty(valMap.get("id")) ? valMap.get("id").toString() : "";
                    String table = !ObjectUtils.isEmpty(valMap.get(TABLE)) ? valMap.get(TABLE).toString() : "";
                    String savetable = !ObjectUtils.isEmpty(valMap.get(SAVE_TABLE)) ? valMap.get(SAVE_TABLE).toString()
                            : "";
                    StringBuilder inData = new StringBuilder();
                    Map<String, Object> inDataMap = new LinkedHashMap<>();

                    Map<String, Object> entity = BeanUtil.beanToMap(o);
                    for (Field field : o.getClass().getDeclaredFields()) {
                        String name = field.getName();
                        if (ArrayUtils.contains(sensitiveFields, name)) {
                            String val = StringUtils.EMPTY;
                            if (!ObjectUtils.isEmpty(entity.get(name))) {
                                val = entity.get(name).toString();
                            }
                            if (StringUtils.isNotEmpty(val)) {
                                inData.append(val);
                                inDataMap.put(name, val);
                            }
                        }
                    }
                    if (inData.length() > 0) {
                        String secretKey = getSecretKey(fieldConfig, o.getClass(), MyConstants.INTEGRITY);
                        if (ObjectUtils.isNotEmpty(secretKey)) {
                            String idVal = entity.get(id).toString();
                            String integrityData1 = integrityApi.getIntegrityData(idVal, table, savetable);
                            log.info("数据库中完整性数据：{}", integrityData1);
                            String integrityData = cryptoApi.mac(inData.toString(), secretKey);
                            if (!integrityData.equals(integrityData1)) {
                                String msg = "数据ID：【" + idVal + "】完整性校验失败";
                                throw new IntegrityDataException(msg);
                            }
                            log.error("数据完整性校验通过！");

                        } else {
                            log.error("密钥不存在，请检查密钥！");
                        }


                    }
                }
            }
        } else {
            if (null != result && fieldConfig.containsKey(result.getClass())) {
                this.process(result);
            }
        }
    }


    /**
     * 对返回值对象的具体处理过程.
     *
     * @param resultObj 返回值对象
     * @throws IllegalAccessException 反射可能抛出的异常
     */
    private void process(Object resultObj) {
        if (resultObj == null) {
            return;
        }
    }

}
